import {useCallback, useReducer, useState} from "react";
import {useMountedRef} from "utils";

interface State<D> {
	error: Error | null;
	data: D | null;
	stat: 'idle' | 'loading' | 'error' | 'success'
}

const defaultInitialState: State<null> = {
	stat: 'idle',
	data: null,
	error: null
}
const defaultConfig = {
	throwOnError: false
}
const useSafeDispatch = <T>(dispatch: (...args: T[]) => void) => {
	const mountedRef = useMountedRef()
	return useCallback((...args: [T]) => (mountedRef.current ? dispatch(...args) : void 0), [dispatch, mountedRef])
}
export const useAsync = <D>(initatialState?: State<D>, initialConfig?: typeof defaultConfig) => {
	const config = {...defaultConfig, initialConfig}
	const [state, dispatch] = useReducer((state: State<D>, action: Partial<State<D>>) => ({...state, ...action}), {
		...defaultInitialState,
		...initatialState
	})
	const safeDispatch = useSafeDispatch(dispatch)
	const [retry, setRetry] = useState(() => () => {
	})
	//useState直接传入函数的意义是:惰性化初始化(进来就加载),所以要用useState保存函数,不能直接传入函数
	const setData = useCallback((data: D) =>
		safeDispatch({
			data,
			stat: 'success',
			error: null,
		}), [safeDispatch])
	const setError = useCallback((error: Error) =>
		safeDispatch({
			error,
			stat: 'error',
			data: null
		}), [safeDispatch])
	// run:用来触发异步请求
	const run = useCallback((promise: Promise<D>, runConfig?: { retry: () => Promise<D> }) => {
		if (!promise || !promise.then) {
			throw new Error('请传入 Promise 类型数据')
		}
		setRetry(() => () => {
			if (runConfig?.retry) {
				run(runConfig?.retry(), runConfig)
			}
		})
		safeDispatch({stat: 'loading'})
		return promise.then(data => {
			setData(data)
			return data
		}).catch(error => {
			//catch 会消化异常 如果不主动抛出外面接收不到以上的
			setError(error)
			if (config.throwOnError) return Promise.reject(error)
			return Promise.reject(error)
		})
		// }, [config.throwOnError, mountedRef, setData, state,setError])
	}, [config.throwOnError, setData, setError,safeDispatch])

	return {
		isIdle: state.stat === 'idle',
		isLoading: state.stat === 'loading',
		isError: state.stat === 'error',
		isSuccess: state.stat === 'success',
		run,
		setData,
		setError,
		//retry 被调用时重新跑一遍run,让state刷新一遍
		retry,
		...state
	}

}
